//! ## Error & Result Types in CS2 System
//!
//! CS2 System defines its own error type `Error` and result type `Result` for error
//! handling.

use std::{ffi, result};

use thiserror::Error;

/// Error type for cs2s.
#[repr(u8)]
#[derive(Error, Debug, Clone, PartialEq, Eq)]
pub enum Error {
    #[error("Libc error ({errno}): {strerr}", strerr = {
        // Safety:
        // - `errno` in `Error::LibcError` is a valid errno.
        // - `errno` in libc is thread-local.
        unsafe {
            ffi::CStr::from_ptr(libc::strerror(*errno))
                .to_string_lossy()
        }
    })]
    LibcError { errno: i32 } = 0,
    #[error("Invalid magic number: actual = {actual:#X}, expected = {expected:#X}")]
    InvalidMagic { actual: u32, expected: u32 },
    #[error("Invalid name: {name}")]
    InvalidName { name: String },
    #[error("Invalid client: pid = {pid}")]
    InvalidClient { pid: u32 },
    #[error("Invalid command: {msg}")]
    InvalidCommand { msg: String },
    #[error("Memory not enough: required = {required} B, remain = {free} B")]
    MemNotEnough { required: usize, free: usize },
    #[error("Path not found: {path}")]
    PathNotFound { path: String },
    #[error("Table not found: {path}")]
    TableNotFound { path: String },
    #[error("Need enlargement")]
    EnlargementNeeded,
}

impl Error {
    /// Get the discriminant of the error.
    pub fn discriminant(&self) -> u8 {
        // Safety: `Error` is repr(u8).
        unsafe { *(self as *const Self as *const u8) }
    }
}

/// Result type for cs2s.
pub type Result<T> = result::Result<T, Error>;

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn discriminant() {
        assert_eq!(Error::LibcError { errno: 0 }.discriminant(), 0);
    }
}
